package com.xms.sdk.client;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xms.core.data.Entity;
import com.xms.core.enums.AccessRightValue;
import com.xms.core.enums.OperationStage;
import com.xms.core.enums.OperationType;
import com.xms.core.exception.XmsException;
import com.xms.plugin.service.IEntityPluginExecutor;
import com.xms.schema.core.CascadeDeleteType;
import com.xms.schema.core.RelationShipType;
import com.xms.schema.entity.Attribute;
import com.xms.schema.entity.RelationShip;
import com.xms.schema.extension.AttributeExt;
import com.xms.schema.service.attribute.IAttributeFinderService;
import com.xms.schema.service.relationship.IRelationShipFinderService;
import com.xms.sdk.core.RetrieverParameter;
import com.xms.sdk.core.query.ConditionOperator;
import com.xms.sdk.core.query.FilterExpression;
import com.xms.sdk.core.query.LogicalOperator;
import com.xms.sdk.core.query.QueryExpression;
import com.xms.sdk.data.IOrganizationDataProvider;
import com.xms.sdk.data.IOrganizationDataRetriever;
import com.xms.sdk.event.EntityDeletedEvent;
import com.xms.sdk.event.EntityDeletingEvent;
import com.xms.sdk.query.QueryResult;
import com.xms.sdk.service.IAggregateService;
import com.xms.sdk.service.IDataDeleter;
import com.xms.utils.CollectionUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.sql.SQLException;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * <p>
 * DataDeleter
 * </p>
 *
 * @author migo
 * @since 2021/9/6
 */
@Service
public class DataDeleter extends DataProviderBase implements IDataDeleter {
    private IOrganizationDataProvider _organizationDataProvider;
    private IOrganizationDataRetriever _organizationDataRetriever;
    private IAttributeFinderService _attributeFinderService;
    private IRelationShipFinderService _relationShipFinderService;
    private IAggregateService _aggregateService;
    private IEntityPluginExecutor _entityPluginExecutor;

    @Autowired
    public void DataDeleter(IOrganizationDataProvider organizationDataProvider
            , IOrganizationDataRetriever organizationDataRetriever
            , IAttributeFinderService attributeFinderService
            , IRelationShipFinderService relationShipFinderService
            , IAggregateService aggregateService
            , IEntityPluginExecutor entityPluginExecutor) {
        _organizationDataProvider = organizationDataProvider;
        _organizationDataRetriever = organizationDataRetriever;
        _attributeFinderService = attributeFinderService;
        _relationShipFinderService = relationShipFinderService;
        _aggregateService = aggregateService;
        _entityPluginExecutor = entityPluginExecutor;
    }

    @Override
    public boolean delete(Entity record, boolean ignorePermissions) throws XmsException, SQLException {
        com.xms.schema.entity.Entity entityMetadata = getEntityMetaData(record.Name);
        List<Attribute> attributeMetadatas = _attributeFinderService.getByEntityId(entityMetadata.getEntityid());
        return this.deleteCore(entityMetadata, attributeMetadatas, record, ignorePermissions);
    }

    @Override
    public boolean delete(String name, UUID id, boolean ignorePermissions) throws Exception {
        com.xms.schema.entity.Entity entityMetadata = getEntityMetaData(name);
        List<Attribute> attributeMetadatas = _attributeFinderService.getByEntityId(entityMetadata.getEntityid());
        Attribute primaryAttr = attributeMetadatas.stream().filter(n -> AttributeExt.typeIsPrimaryKey(n)).findFirst().get();
        String primarykey = primaryAttr.getName();
        QueryExpression query = new QueryExpression(name);
        query.ColumnSet.AllColumns = true;
        query.Criteria.addCondition(primarykey, ConditionOperator.Equal, id);
        Entity record = _organizationDataRetriever.getOne(RetrieverParameter.build(query, _currentUser).setIgnorePermissions(true)).data;
        return this.deleteCore(entityMetadata, attributeMetadatas, record, ignorePermissions);
    }

    @Override
    public boolean delete(String name, List<UUID> ids, boolean ignorePermissions) throws Exception {
        boolean isSuccess = false;
        for (UUID id : ids) {
            isSuccess = this.delete(name, id, ignorePermissions);
        }
        return isSuccess;
    }

    @Override
    public boolean delete(Entity record) throws XmsException, SQLException {
        return this.delete(record, false);
    }

    @Override
    public boolean delete(String name, UUID id) throws Exception {
        return this.delete(name, id, false);
    }

    @Override
    public boolean delete(String name, List<UUID> ids) throws Exception {
        return this.delete(name, ids, false);
    }

    private boolean deleteCore(com.xms.schema.entity.Entity entityMetadata, List<com.xms.schema.entity.Attribute> attributeMetadatas, Entity record, boolean ignorePermissions) throws XmsException, SQLException {
        if (!ignorePermissions) {
            verifyEntityPermission(record, AccessRightValue.Delete, entityMetadata);
        }
        //cascade relationship, 1: N
        List<RelationShip> relationships = _relationShipFinderService.list(new LambdaQueryWrapper<RelationShip>()
                .eq(RelationShip::getReferencedentityid, entityMetadata.getEntityid()));
        //check referenced
        List<RelationShip> cascadeDeleteRestrict = relationships.stream().filter(n -> n.getReferencedentityid().equals(entityMetadata.getEntityid()) && n.getCascadedelete() == CascadeDeleteType.Restrict && n.getRelationshiptype() == RelationShipType.ManyToOne.value()).collect(Collectors.toList());

        Attribute primaryAttr = attributeMetadatas.stream().filter(n -> AttributeExt.typeIsPrimaryKey(n)).findFirst().get();
        String primarykey = primaryAttr.getName();

        record.setIdName(primarykey);
        UUID recordId = record.getIdValue();
        for (RelationShip cdr : cascadeDeleteRestrict) {
            FilterExpression filter = new FilterExpression(LogicalOperator.And)
                    .addCondition(cdr.getReferencingattributename(), ConditionOperator.Equal, recordId);
            long referencingRecord = _aggregateService.count(cdr.getReferencingentityname(), filter);
            if (referencingRecord > 0) {
                return onException("存在引用: " + cdr.getReferencedentityname());
            }
        }
        boolean result = false;
        try {
            internalOnDelete(record, OperationStage.PreOperation, entityMetadata, attributeMetadatas);
            //delete related records
            List<RelationShip> cascadeDelete = relationships.stream().filter(n -> n.getReferencedentityid().equals(entityMetadata.getEntityid()) && n.getCascadedelete() == CascadeDeleteType.All && n.getRelationshiptype() == RelationShipType.ManyToOne.value()).collect(Collectors.toList());
            if (CollectionUtil.notEmpty(cascadeDelete)) {
                deleteRelatedRecords(entityMetadata, attributeMetadatas, cascadeDelete, recordId);
            }
            //delete main record
            result = _organizationDataProvider.delete(record.Name, recordId, primarykey);
            if (result) {
                internalOnDelete(record, OperationStage.PostOperation, entityMetadata, attributeMetadatas);
            }
        } catch (Exception e) {
            onException(e);
        }

        return result;
    }

    private boolean deleteRelatedRecords(com.xms.schema.entity.Entity entityMetaData, List<com.xms.schema.entity.Attribute> attributeMetadatas, List<RelationShip> relationships, UUID parentId) throws XmsException, SQLException {
        boolean result = true;
        Attribute primaryKeyField = attributeMetadatas.stream().filter(x -> AttributeExt.typeIsPrimaryKey(x)).findFirst().get();
        for (RelationShip rs : relationships) {
            //delete the 'N' records
            QueryExpression query = new QueryExpression(rs.getReferencingentityname());
            query.ColumnSet.addColumn(primaryKeyField.getName());
            query.Criteria.addCondition(rs.getReferencingattributename(), ConditionOperator.Equal, parentId);
            QueryResult<List<Entity>> queryResult = _organizationDataRetriever.list(RetrieverParameter.build(query, _currentUser).setIgnorePermissions(true).setIncludeNameField(false));
            if (CollectionUtil.notEmpty(queryResult.data)) {
                List<Entity> datas = queryResult.data;
                //getEntityMetaData(datas.get(0).Name);
                com.xms.schema.entity.Entity entityMetadata2 = queryResult.metadataDescriptor.entityList.get(0);
                List<RelationShip> relationships2 = _relationShipFinderService.list(new LambdaQueryWrapper<RelationShip>()
                        .eq(RelationShip::getReferencedentityid, entityMetaData.getEntityid())
                        .eq(RelationShip::getRelationshiptype, RelationShipType.ManyToOne.value())
                        .eq(RelationShip::getCascadedelete, CascadeDeleteType.All));
                for (Entity item : datas) {
                    if (CollectionUtil.notEmpty(relationships2)) {
                        deleteRelatedRecords(entityMetadata2, attributeMetadatas, relationships2, item.getIdValue());
                    }
                    internalOnDelete(item, OperationStage.PreOperation, entityMetaData, attributeMetadatas);
                    result = _organizationDataProvider.delete(item.Name, item.getIdValue());
                    if (result) {
                        internalOnDelete(item, OperationStage.PostOperation, entityMetaData, attributeMetadatas);
                    }
                }
            }
        }
        return result;
    }

    private void internalOnDelete(Entity data, OperationStage stage, com.xms.schema.entity.Entity entityMetadata, List<com.xms.schema.entity.Attribute> attributeMetadatas) throws XmsException {
        //plugin
        try {
            _entityPluginExecutor.execute(OperationType.Delete, stage, data, entityMetadata, attributeMetadatas);
        } catch (Exception e) {
            onException(e);
        }
        publishEvents(data, stage, entityMetadata, attributeMetadatas);
    }

    // 发布事件
    private void publishEvents(Entity data, OperationStage stage, com.xms.schema.entity.Entity entityMetadata, List<Attribute> attributeMetadatas) {
        if (stage == OperationStage.PreOperation) {
            _eventPublisher.publish(new EntityDeletingEvent(data, entityMetadata, attributeMetadatas));
        } else if (stage == OperationStage.PostOperation) {
            _eventPublisher.publish(new EntityDeletedEvent(data, entityMetadata, attributeMetadatas));
        }
    }
}
